/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	RenderTechnique.cpp
*
*	Comments	-	See RenderTechnique.h
*
**************************************************************************************/
#include "../Include/RenderTechnique.h"
#include "../Include/FileSystem.h"
#include "../Include/ResourceCache.h"
#include "../Include/Engine.h"
#include "../Include/GlobalDirectory.h"
#include "../Include/XMLParser.h"
#include "../Include/GraphicsResourceManager.h"
#include "../Include/GeometryElementInfo.h"
#include "../Include/MaterialElementInfo.h"
#include "../Include/GlobalDirectory.h"
#include "../Include/ShaderMacro.h"
#include "../Include/Array.h"
#include "../Include/GraphicsMapping.h"
#include "../Include/DepthStencilState.h"
#include "../Include/BlendState.h"
#include "../Include/RasterizerState.h"

namespace Pulse
{
	RenderTechnique::RenderTechnique( void )
	{

	}

	RenderTechnique::~RenderTechnique( void )
	{
	}

	BOOL RenderTechnique::CreateResource( BaseResDesc *pDesc )
	{
		if ( pDesc->GetType() != GetType() )
			return FALSE;

		m_desc = *((RenderTechniqueDesc*)pDesc);

		if ( PerformInitialize( m_desc.IsAsyncLoad() ) == EErrorCode::OKAY )
			SetFlagCreated();

		return IsCreated();
	}

	BOOL RenderTechnique::DestroyResource( void )
	{
		UnloadResource();
		SetFlagDestroyed();	
		return TRUE;	
	}

	BOOL RenderTechnique::UnloadResource( void )
	{
		Cleanup();
		SetFlagDisabled();
		return TRUE;	
	}

	BOOL RenderTechnique::LoadResource( void )
	{
		if ( Initialize() == EErrorCode::OKAY )
		{
			SetFlagRestored();	
			return TRUE;	
		}

		return FALSE;
	}

	EErrorCode::Type RenderTechnique::Initialize( void )
	{
		if ( m_desc.m_filename.IsEmpty() )
		{
			PSX_PushError( "No filename specified." );
			return EErrorCode::INVALID_PARAMETER;
		}

		String fullPath = String(PSX_PATH_RENDER_TECHNIQUES) +
			String(Engine::GetInstance()->GetRenderer()->GetRenderPath()->GetTechniquesPath()) + m_desc.m_filename;
		XMLDocumentPtr pDoc = XMLParser::Load( fullPath.GetBuffer() );

		if ( pDoc.IsNull() )
		{
			PSX_PushError( "Failed to load render technique file." );
			return EErrorCode::FILE_NOT_FOUND;
		}

		EErrorCode::Type retCode;
		XMLElementPtr pElem = pDoc->FindChild(PSX_String("Technique"));

		if ( pElem.IsNull() == FALSE )
		{
			retCode = ProcessTechnique( pElem );
		
			if ( retCode != EErrorCode::OKAY )
				return retCode;
		}
		else
		{
			PSX_PushError( "Invalid render technique file." );
			return EErrorCode::INVALID_FORMAT;
		}

		return EErrorCode::OKAY;
	}

	void RenderTechnique::Cleanup( void )
	{
		for ( SIZE_T i = 0; i < m_renderPasses.GetSize(); ++i )
			delete m_renderPasses[i];
	
		m_renderPasses.Clear();
	}
	
	RenderPass * RenderTechnique::GetRenderPass( UPOS_T index )
	{
		if ( index < GetNumRenderPasses() )
		{
			return m_renderPasses[index];
		}

		return PSX_NULL;
	}

	void RenderTechnique::AddEffectRegister( EffectRegister *pRegister ) 
	{
		if ( pRegister == PSX_NULL )
		{
			return;
		}

		// Make sure we don't have a duplicate!
		for ( SIZE_T i = 0; i < m_effectRegisters.GetSize(); ++i )
		{
			// Just check for the effectID
			if ( m_effectRegisters[i].effectID == pRegister->effectID )
			{
				PSX_PushWarning( "EffectID already registerd for this render technique." );
				return;
			}
		}

		m_effectRegisters.PushBack( *pRegister ); 
	}

	EffectRegister * RenderTechnique::GetEffectRegisters( void )
	{
		if ( m_effectRegisters.GetSize() )
		{
			return &m_effectRegisters[0];
		}

		return PSX_NULL;
	}

	EffectRegister * RenderTechnique::GetEffectRegister( UPOS_T index )
	{
		if ( index < m_effectRegisters.GetSize() )
			return &m_effectRegisters[index];

		return PSX_NULL;
	}

	EErrorCode::Type RenderTechnique::ProcessTechnique( XMLElementPtr pElem )
	{
		EErrorCode::Type retCode;
		XMLElementPtr pChild;

		// Effect registers is moved in the techniques library
		{
			//pChild = pElem->FindChild(PSX_String("EffectRegisters"));

			//if ( pChild.IsNull() )
			//{
			//	PSX_PushError("Missing EffectRegisters info.");
			//	return EErrorCode::INVALID_FORMAT;
			//}

			//retCode = ProcessEffectRegisters( pChild );
	
			//if ( retCode != EErrorCode::OKAY )
			//	return retCode;
		}

		pChild = pElem->FindChild( PSX_String("RenderPasses") );

		if ( pChild.IsNull() )
		{
			PSX_PushError("Missing RenderPasses info.");
			return EErrorCode::INVALID_FORMAT;
		}

		retCode = ProcessRenderPasses( pChild );

		if ( retCode != EErrorCode::OKAY )
			return retCode;

		return EErrorCode::OKAY;
	}

	EErrorCode::Type RenderTechnique::ProcessEffectRegisters( XMLElementPtr pElem )
	{
		//EErrorCode::Type retCode;
		XMLElementPtr pChild = pElem->GetFirstChild();

		while ( pChild.IsNull() == FALSE )
		{
			// Process each register
			if ( pChild->GetName() == PSX_String("Register") )
			{
				EffectRegister newRegister;

				String buffer = pChild->FindChild(PSX_String("EffectID"))->GetValue();
				newRegister.effectID = buffer.ToUINT16();
				buffer = pChild->FindChild(PSX_String("Priority"))->GetValue();
				newRegister.priority= (TechniquePriority)buffer.ToUINT8();

				m_effectRegisters.PushBack( newRegister );
			}

			pChild = pChild->GetNextSibling();
		}

		return EErrorCode::OKAY;
	}

	EErrorCode::Type RenderTechnique::ProcessRequirements( XMLElementPtr pElem, RenderPass *pPass )
	{
		// The requirements section is split into two parts: Geometry and Material

		XMLElementPtr pChild = pElem->FindChild( PSX_String("Geometry") );

		if ( pChild.IsNull() )
		{
			PSX_PushError( "Missing Geometry requirements." );
			return EErrorCode::INVALID_PARAMETER;
		}
		
		{
			XMLElementPtr pGeometryElement = pChild->GetFirstChild();

			while ( pGeometryElement.IsValid() )
			{
				if ( pGeometryElement->GetName() == PSX_String("Element") )
				{
					GeometryElementRequirement requirement;

					GeometryElementInfo::GetRequirementElement( pGeometryElement, &requirement );
			
					if ( requirement.m_type != EVertexElement::UNKNOWN )
						pPass->AddGeometryRequirement( &requirement );
					else
						PSX_PushWarning( "Skipping. Invalid vertex element type." );
				}

				pGeometryElement = pGeometryElement->GetNextSibling();
			}
		}

		pChild = pElem->FindChild( PSX_String("Material") );

		if ( pChild.IsNull() )
		{
			PSX_PushError( "Missing Material requirements." );
			return EErrorCode::INVALID_FORMAT;
		}

		{
			XMLElementPtr pMaterialRequirement = pChild->GetFirstChild();

			while ( pMaterialRequirement.IsValid() )
			{
				if ( pMaterialRequirement->GetName() == PSX_String("Element") )
				{
					MaterialElementRequirement requirement;

					MaterialElementInfo::GetRequirementElement( pMaterialRequirement, &requirement );

					if ( requirement.m_type != EParameter::UNKNOWN )
						pPass->AddMaterialRequirement( &requirement );
					else
					{
						PSX_PushWarning( "Skipping. Invalid material element type." );
					}
				}

				pMaterialRequirement = pMaterialRequirement->GetNextSibling();
			}
		}

		return EErrorCode::OKAY;
	}

	EErrorCode::Type RenderTechnique::ProcessRenderPasses( XMLElementPtr pElem )
	{
		//EErrorCode::Type retCode;
		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
		XMLElementPtr pPasses = pElem->GetFirstChild();
		XMLElementPtr pRequirements;
		XMLElementPtr pRenderStates;
		XMLElementPtr pShaderTag;
		RenderPass *pNewPass;

		// Parse each pass
		while ( pPasses.IsNull() == FALSE )
		{
			if ( pPasses->GetName() == PSX_String("Pass") )
			{
				pNewPass = new RenderPass;

				// Get RenderView type and RenderViewStage type
				String renderViewStage = pPasses->FindChild( PSX_String("RenderViewStage") )->GetValue();
				
				pNewPass->SetRenderViewStage( GraphicsMapping::ToRenderViewStage( renderViewStage ) );

				// Get geometry and material requirements
				pRequirements = pPasses->FindChild( PSX_String("Requirements") );

				if ( pRequirements.IsNull() )
				{
					PSX_PushError( "Missing Requirements info." );
					delete pNewPass;
					return EErrorCode::INVALID_FORMAT;
				}

				EErrorCode::Type retCode = ProcessRequirements( pRequirements, pNewPass );

				if ( retCode != EErrorCode::OKAY )
				{
					delete pNewPass;
					return retCode;
				}

				// Process render states. Render states are optional. So just skip
				//	if we don't have one
				pRenderStates = pPasses->FindChild( PSX_String("RenderStates") );

				if ( pPasses.IsValid() )
				{
					retCode = ProcessRenderStates( pRenderStates, pNewPass );

					if ( retCode != EErrorCode::OKAY )
					{
						delete pNewPass;
						return retCode;
					}
				}

				// Create shaders
				pShaderTag = pPasses->FindChild( PSX_String("Shaders") )->GetFirstChild();

				while ( pShaderTag.IsNull() == FALSE )
				{
					// Figure what type of shader we're creating
					String shaderType = pShaderTag->GetName();
					String filename = pShaderTag->FindChild(PSX_String("File"))->GetValue();
					String functionName = pShaderTag->FindChild(PSX_String("FunctionName"))->GetValue();
					String resourceName = m_desc.m_filename + filename + functionName;
					ShaderDesc shaderDesc;
					Shader *pBaseShader = PSX_NULL; // Temporarily store the created shader to do error check.

					// Get list of shader macros if available.
					XMLElementPtr pDefines = pShaderTag->FindChild( PSX_String("Defines") );

					if ( pDefines.IsValid() )
					{
						XMLElementPtr pDefine = pDefines->GetFirstChild();

						while ( pDefine.IsValid() )
						{
							ShaderMacro newMacro;

							newMacro.name = pDefine->FindChild( PSX_String("Name") )->GetValue();
							newMacro.value = pDefine->FindChild( PSX_String("Value") )->GetValue();
							shaderDesc.AddMacro( &newMacro );

							pDefine = pDefine->GetNextSibling();
						};
					}

					if ( shaderType == PSX_String("VertexShader") )
					{
						shaderDesc.SetAsVertexShader( filename.GetBuffer(), functionName.GetBuffer() );
						VertexShader *pShader = pGM->CreateVertexShader( resourceName.GetBuffer(), &shaderDesc );						
						pNewPass->SetVertexShader( pShader );
						pBaseShader = pShader;
						pShader->Release();
					}
					else if ( shaderType == PSX_String("PixelShader") )
					{
						shaderDesc.SetAsPixelShader( filename.GetBuffer(), functionName.GetBuffer() );
						PixelShader *pShader = pGM->CreatePixelShader( resourceName.GetBuffer(), &shaderDesc );
						pNewPass->SetPixelShader( pShader );
						pBaseShader = pShader;
						pShader->Release();
					}
					else if ( shaderType == PSX_String("ComputeShader") )
					{
						shaderDesc.SetAsComputeShader( filename.GetBuffer(), functionName.GetBuffer() );
						ComputeShader *pShader = pGM->CreateComputeShader( resourceName.GetBuffer(), &shaderDesc );
						pNewPass->SetComputeShader( pShader );
						pBaseShader = pShader;
						pShader->Release();
					}
					else // TODO: Implement other shader type creation
					{
						PSX_PushError( "Other Technique shader type creation not yet implemented." );
					}

					if ( pBaseShader == PSX_NULL )
					{
						Cleanup();
						return EErrorCode::_ERROR;
					}

					pShaderTag = pShaderTag->GetNextSibling();
				}

				// TODO: Parse render pass states here...

				m_renderPasses.PushBack( pNewPass );
			}

			pPasses = pPasses->GetNextSibling();
		}

		return EErrorCode::OKAY;
	}

	EErrorCode::Type RenderTechnique::ProcessRenderStates( XMLElementPtr pElem, RenderPass *pNewPass )
	{
		// We may have a RenderStates tag but may have an empty content. And that is okay! :)
		GraphicsResourceManager *pGM		= GraphicsResourceManager::GetInstance();
		XMLElementPtr pBlendState			= pElem->FindChild( PSX_String("BlendState") );
		XMLElementPtr pDepthStencilState	= pElem->FindChild( PSX_String("DepthStencilState") );
		XMLElementPtr pRasterState			= pElem->FindChild( PSX_String("RasterizerState") );

		// Get Depth Stencil state if available
		if ( pDepthStencilState.IsValid() && pDepthStencilState->GetValue().IsEmpty() == FALSE )
		{
			DepthStencilStateDesc desc;

			desc.SetToLoad( pDepthStencilState->GetValue().GetBuffer() );

			pNewPass->m_pDepthStencilState = pGM->CreateDepthStencilState( pDepthStencilState->GetValue().GetBuffer(), &desc );

			if ( pNewPass->m_pDepthStencilState == PSX_NULL )
			{
				PSX_PushError( "Failed to create depth-stencil state." );
				return EErrorCode::GRAPHICS;
			}
		}
		else
		{
			pNewPass->m_pDepthStencilState = PSX_NULL;
		}

		// Get blendstate if available
		if ( pBlendState.IsValid() && pBlendState->GetValue().IsEmpty() == FALSE )
		{
			BlendStateDesc desc;

			desc.SetToLoad( pBlendState->GetValue().GetBuffer() );

			pNewPass->m_pBlendState = pGM->CreateBlendState( pBlendState->GetValue().GetBuffer(), &desc );
		
			if ( pNewPass->m_pBlendState == PSX_NULL )
			{
				PSX_PushError( "Failed to create blend state." );
				return EErrorCode::GRAPHICS;
			}
		}
		else
		{
			pNewPass->m_pBlendState = PSX_NULL;
		}

		// Get rasterizer state if available
		if ( pRasterState.IsValid() && pRasterState->GetValue().IsEmpty() == FALSE )
		{
			RasterizerStateDesc desc;

			desc.SetToLoad( pRasterState->GetValue().GetBuffer() );

			pNewPass->m_pRasterizerState = pGM->CreateRasterizerState( pRasterState->GetValue().GetBuffer(), &desc );

			if ( pNewPass->m_pRasterizerState == PSX_NULL )
			{
				PSX_PushError( "Failed to create rasterizer state." );
				return EErrorCode::GRAPHICS;
			}
		}
		else
		{
			pNewPass->m_pRasterizerState = PSX_NULL;
		}

		return EErrorCode::OKAY;
	}

}